﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
using System.Windows.Forms.Design;
using System.Diagnostics;

namespace SyringeControl.ControlConfiguration.Events
{
    class EventPropertyTab: PropertyTab
    {
        EventTabModel _model;

        public EventPropertyTab()
        {
            //HACK: Hate doing this, but I've yet have to discover way by which tab can receive parameters
            _model = SyringeContainer.Instance.Resolve<EventTabModel>();
        }

        public override System.ComponentModel.PropertyDescriptorCollection GetProperties(object component, Attribute[] attributes)
        {
            _model.Component = component;
            return _model.GetEventList(attributes);
        }

        public override string TabName
        {
            get { return "Events"; }
        }
    }

    public class EventLoggingSettings
    {
        class EventDescriptorComparer : IEqualityComparer<EventDescriptor>
        {
            #region Члены IEqualityComparer<EventDescriptor>

            public bool Equals(EventDescriptor x, EventDescriptor y)
            {
                if (x == y) return true;
                if (x == null || y == null) return false;
                return x.EventType == y.EventType && x.Name == y.Name;
            }

            public int GetHashCode(EventDescriptor obj)
            {
                return obj.EventType.GetHashCode() ^ obj.Name.GetHashCode();
            }

            #endregion
        }

        Dictionary<EventDescriptor, Type> _types = new Dictionary<EventDescriptor, Type>(new EventDescriptorComparer());
        public Type GetLoggerType(EventDescriptor eventDescr)
        {
            Type loggerType;
            _types.TryGetValue(eventDescr, out loggerType);
            return loggerType;
        }

        public void SetLoggerType(EventDescriptor eventDescr, Type loggerType)
        {
            _types[eventDescr] = loggerType;
        }
        Dictionary<EventDescriptor, Delegate> _handlers = new Dictionary<EventDescriptor, Delegate>(new EventDescriptorComparer());
        public Delegate GetLogger(EventDescriptor eventDescriptor)
        {
            Delegate loggerType;
            _handlers.TryGetValue(eventDescriptor, out loggerType);
            return loggerType;
        }
        public void SetLogger(EventDescriptor eventDescriptor, Delegate handler)
        {
            _handlers[eventDescriptor] = handler;
        }
    }

    public interface IEventLoggingSettingsProvider
    {
        EventLoggingSettings GetSettingsForComponent(object component);
        void SetSettingsForComponent(object component, EventLoggingSettings settings);
    }

    class DefaultEventSettingsProvider : IEventLoggingSettingsProvider
    {
        IDictionary<Type, EventLoggingSettings> _settings = new Dictionary<Type, EventLoggingSettings>();
        #region Члены IEventLoggingSettingsProvider

        public EventLoggingSettings GetSettingsForComponent(object component)
        {
            EventLoggingSettings settings;
            if (_settings.TryGetValue(component.GetType(), out settings))
                return settings;
            return new EventLoggingSettings();
        }

        public void SetSettingsForComponent(object component, EventLoggingSettings settings)
        {
            _settings[component.GetType()] = settings;
        }

        #endregion
    }

    public class EventTabModel
    {
        readonly IEventLogger[] _supportedLoggers;
        readonly UITypeEditor _editor;
        readonly IAdapterBuilder _builder;
        readonly IEventLoggingSettingsProvider _settingsProvider;

        public EventTabModel(IEnumerable<IEventLogger> supportedLoggers, IAdapterBuilder builder, 
            IEventLoggingSettingsProvider settingsProvider)
        {
            _supportedLoggers = supportedLoggers.ToArray();
            _editor = new HandlerSelector(supportedLoggers);
            _builder = builder;
            _settingsProvider = settingsProvider;
        }

        class HandlerSelector : ObjectSelectorEditor
        {
            readonly IDictionary<string, IEventLogger> _loggers;

            public HandlerSelector(IEnumerable<IEventLogger> loggers)
            {
                _loggers = loggers.ToDictionary(l => l.ToString());
            }

            public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
            {
                return UITypeEditorEditStyle.DropDown;
            }
            protected override void FillTreeWithData(Selector selector, ITypeDescriptorContext context, IServiceProvider provider)
            {
                if (selector.GetNodeCount(true) == 0)
                {
                    foreach (var record in _loggers)
                        selector.AddNode(record.Key, record.Value, null);
                }
            }
        }

        class MyEventDescriptor : PropertyDescriptor
        {
            public readonly EventDescriptor Descriptor;
            string _handlerDescription;

            public MyEventDescriptor(Type compType, MemberDescriptor descr, Attribute[] attrs, UITypeEditor editor)
                : base(descr, attrs)
            {
                _compType = compType;
                Descriptor = (EventDescriptor)descr;
                _editor = editor;
            }
            UITypeEditor _editor;
            public override object GetEditor(Type editorBaseType)
            {
                if(typeof(UITypeEditor).IsAssignableFrom(editorBaseType))
                    return _editor;
                return base.GetEditor(editorBaseType);
            }

            public override bool CanResetValue(object component)
            {
                return true;
            }

            Type _compType;
            public override Type ComponentType
            {
                get { return _compType; }
            }

            public override object GetValue(object component)
            {
                return _handlerDescription;
            }

            public override bool IsReadOnly
            {
                get { return false; }
            }

            public override Type PropertyType
            {
                get { return Descriptor.EventType; }
            }

            public override void ResetValue(object component)
            {
                OnValueChanged(this, new LoggerChangedEventArgs{Name = this.Name, EventLogger=new NullEventLogger()});
            }

            public override void SetValue(object component, object value)
            {
                OnValueChanged(this, new LoggerChangedEventArgs { Name = this.Name, EventLogger = value as IEventLogger});
                _handlerDescription = (value ?? "").ToString();
            }

            public override bool ShouldSerializeValue(object component)
            {
                return true;
            }
        }

        class LoggerChangedEventArgs : EventArgs
        {
            public string Name { get; set; }
            public IEventLogger EventLogger { get; set; }
        }

        EventLoggingSettings _settings;
        Object _component;
        IList<MyEventDescriptor> _descriptors;
        public Object Component 
        { 
            get { return _component; } 
            set
            {
                if (_settings != null)
                {
                    _settingsProvider.SetSettingsForComponent(_component, _settings);
                }
                _component = value;
                _settings = GetSettings(value);
                _descriptors = GetAllEvents(value);
                ApplySettingsToEvents();
            } 
        }

        private void ApplySettingsToEvents()
        {
            foreach (var prop in _descriptors)
            {
                var loggerType = _settings.GetLoggerType(prop.Descriptor);
                if (loggerType != null && !(loggerType == typeof(NullEventLogger)))
                {
                    Trace.WriteLine(String.Join(",", _supportedLoggers.Select(l => l.GetType().ToString()).ToArray()));
                    var logger = _supportedLoggers.Single(l => l.GetType() == loggerType);
                    prop.SetValue(Component, logger);
                }
            }
        }

        private EventLoggingSettings GetSettings(object value)
        {
            return _settingsProvider.GetSettingsForComponent(value);
        }

        private IList<MyEventDescriptor> GetAllEvents(Object component)
        {
            var propDescriptors = (from ev in TypeDescriptor.GetEvents(component, new Attribute[0]).OfType<EventDescriptor>()
                                   select new MyEventDescriptor(Component.GetType(), ev, new Attribute[0], _editor)).ToArray();
            foreach (var prop in propDescriptors)
            {
                //TODO: make them unsubscribe!!! maybe should just pass'em a callback into the .ctor
                prop.AddValueChanged(prop, OnValueChanged);
            }
            return propDescriptors;
        }

        void OnValueChanged(object component, EventArgs e)
        {
            var prop = component as PropertyDescriptor;
            var args = e as LoggerChangedEventArgs;
            if (prop == null || args == null)
                return;
            var ev = _descriptors.First(d => d.Name == args.Name);
            SetLogger(ev.Descriptor, args.EventLogger);
        }

        
        public void SetLogger(EventDescriptor eventDescr, IEventLogger logger)
        {
            if (logger == null)
                return;
            var descr = eventDescr;
            var oldLoggerType = _settings.GetLoggerType(descr);
            var oldHandler = _settings.GetLogger(descr);
            if (oldHandler != null && oldLoggerType != logger.GetType())
            {
                descr.RemoveEventHandler(Component, oldHandler);
            }
            if (!(logger is NullEventLogger) && _builder.CanBuild(descr.EventType))
            {
                var newHandler = _builder.BuildAdapter(descr.EventType, logger);
                descr.AddEventHandler(Component, newHandler);
                _settings.SetLogger(descr, newHandler);
            }
            _settings.SetLoggerType(descr, logger.GetType());
        }

        public PropertyDescriptorCollection GetEventList(Attribute[] attributes)
        {
            return new PropertyDescriptorCollection(_descriptors.ToArray());
        }
    }
}
